package org.syncsoft.overalt.gui.api.tableDataModel;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;

import org.syncsoft.overalt.domain.common.entity.IEntity;
import org.syncsoft.overalt.domain.common.entity.api.EntityComparator;

/**
 * 
 * @author tptfc
 *
 * @param <T>
 */
@SuppressWarnings("serial")
public abstract  class AbstractEntityTableDataModel<T extends IEntity> extends AbstractTableDataModel {
	/**
	 * row to code
	 */
	private Map<Integer, Integer> codeIndex;
	
	/**
	 * code to row
	 */
	private Map<Integer, Integer> rowIndex;
	
	private Collection<Integer> requiredCodeIndex;
	
	private Collection<String> columnsName;
	
	{
		setCodeIndex(new HashMap<Integer, Integer>());
		setRequiredCodeIndex(new HashSet<Integer>());
		setRowIndex(new HashMap<Integer, Integer>());
	}
	
	public int getCode(int row) {
		return getCodeIndex().get(row);
	}
	
	/**
	 * 
	 * @param columnNames
	 * @param entities
	 */
	public AbstractEntityTableDataModel(Collection<String> columnNames, Collection<T> entities) {
		this(columnNames, entities, null);
	}
	
	/**
	 * 
	 * @param columnNames
	 * @param entities
	 * @param selectedEntities
	 */
	public AbstractEntityTableDataModel(Collection<String> columnNames, Collection<T> entities, Collection<T> selectedEntities) {
		this(columnNames, entities, selectedEntities, null);
	}
	
	/**
	 * 
	 * @param columnNames
	 * @param entities
	 * @param selectedEntities
	 * @param requiredEntities
	 */
	public AbstractEntityTableDataModel(Collection<String> columnNames, Collection<T> entities, Collection<T> selectedEntities, Collection<T> requiredEntities) {
		if (columnNames != null && entities != null) {
			setColumnsName(columnNames);
			setEntities(entities, selectedEntities);
			setRequiredEntities(requiredEntities);
		}
	}

	/**
	 * @precondition entities not is null
	 * @postcondition table entities values changed 
	 * 
	 * @param entities
	 * @param selectedEntities
	 */
	public void setEntities(Collection<T> entities) {
		setDataVector(createValues(getColumnsName().size(), entities, null), getColumnsName().toArray());
		createEntitiyIndexs(entities);
	}

	/**
	 * @precondition entities not is null
	 * @postcondition table entities values changed 
	 * 
	 * @param entities
	 * @param selectedEntities
	 */
	public void setEntities(Collection<T> entities, Collection<T> selectedEntities) {
		setDataVector(createValues(getColumnsName().size(), entities, selectedEntities), getColumnsName().toArray());
		createEntitiyIndexs(entities);
	}
	
	/*
	 * (non-Javadoc)
	 * @see javax.swing.table.DefaultTableModel#isCellEditable(int, int)
	 */
	@Override
	public boolean isCellEditable(int row, int column) {
		boolean canEdit = false;
		
		if (column == 0 && !getRequiredCodeIndex().contains(getCodeIndex().get(row))) {
			canEdit = true;
		}
		
		return canEdit;
	}
	
	/**
	 * 
	 * @param requiredEntities
	 */
	public void setRequiredEntities(Collection<T> requiredEntities) {
		if (requiredEntities != null) {
			for (IEntity entity: requiredEntities) {
				addRequiredEntityCode(entity.getCode());
			}
		}
	}
	
	/**
	 * @precondition code not is null
	 * 
	 * @param code
	 */
	public void addRequiredEntityCode(Integer code) {
		if (code != null) {
			setValueAt(true, getRowIndex().get(code), 0);
			getRequiredCodeIndex().add(code);
		}
	}
	
	/**
	 * @precondition entities != null
	 * @postcondition array of objects in format [selected:boolean] ...
	 * @param entities
	 * @param selectedEntities
	 * @return
	 */
	public  Object[][] createValues(int columnsCount, Collection<T> entities, Collection<T> selectedEntities) {
		Object values[][] = new Object[0][columnsCount];
		if (entities != null) {
			values = new Object[entities.size()][columnsCount];
			
			List<T> sortedEntities = new ArrayList<T>(entities);
			Collections.sort(sortedEntities, new EntityComparator());
			
			int i = 0;
			for (T entity : sortedEntities) {
				values[i][0] = selectedEntities != null ? selectedEntities.contains(entity) : false;
				
				for (int j = 1; j < columnsCount; j++) {
					values[i][1] = getRowItemValue(j, entity, selectedEntities);
				}
				
				i++;
			}
		}
		
		return values;
	}
	
	/**
	 * get item value of column row
	 * 
	 * @precondition the column start with value 1
	 * 
	 * @param column
	 * @param entity
	 * @param selectedEntities
	 * @return
	 */
	public abstract Object getRowItemValue(int column, T entity, Collection<T> selectedEntities);
	
	/**
	 * 
	 * @param communities
	 * @param requiredEntities
	 */
	private void createEntitiyIndexs(Collection<T> communities) {
		if (communities != null) {
			List<T> sortedCommunities = new ArrayList<T>(communities);
			Collections.sort(sortedCommunities, new EntityComparator());
			
			for (int i = 0; i < sortedCommunities.size(); i++) {
				T entity = sortedCommunities.get(i);
				getCodeIndex().put(i, entity.getCode());
				getRowIndex().put(entity.getCode(), i);
			}
		}
	}
	
	//	GETs AND SETs
	protected Map<Integer, Integer> getCodeIndex() {
		return codeIndex;
	}
	protected void setCodeIndex(Map<Integer, Integer> codeIndex) {
		this.codeIndex = codeIndex;
	}
	protected Collection<Integer> getRequiredCodeIndex() {
		return requiredCodeIndex;
	}
	protected void setRequiredCodeIndex(Collection<Integer> requiredCodeIndex) {
		this.requiredCodeIndex = requiredCodeIndex;
	}

	protected Map<Integer, Integer> getRowIndex() {
		return rowIndex;
	}

	protected void setRowIndex(Map<Integer, Integer> rowIndex) {
		this.rowIndex = rowIndex;
	}

	protected Collection<String> getColumnsName() {
		return columnsName;
	}

	protected void setColumnsName(Collection<String> columnsName) {
		this.columnsName = columnsName;
	}
}
